/**
 *  功能：ESP8266 ch340 DS18B20 温度监控物联网配置
 *  作者：tuut
 *  时间：2023-11-28
 *  描述：
 *      1.
 *      2.
 *      3.
*/
#include <Arduino.h>

#include <ESP8266WiFi.h>

//needed for library
#include <DNSServer.h>
// #include <ESP8266WebServer.h>
#include <WiFiManager.h>
//for 支持Json数据格式的头文件
#include <ArduinoJson.h>

//for LED status
//Ticker是ESP32 arduino的自带库,可以设置以固定时间间隔反复执行某个任务,特别适合以固定频率从传感器收集数据
#include <Ticker.h>
Ticker ticker;
//for 数据按钮 --------------------------------------------------------------------------------
//for BUTTON status
#include <OneButton.h>
// Reset Button ------------------------------------------------------------------------------
#define resetButtonPin 10
OneButton button(resetButtonPin, true);
// ESP8266 -----------------------------------------------------------------------------------
void initSystem();
// DS18B20 -----------------------------------------------------------------------------------
#include "src/Sketch_oct31b_ds18b20.h"
Sketch_oct31b_ds18b20 ds18b20;
// TDS -----------------------------------------------------------------------------------
#include "src/Sketch_oct31b_tds.h"
Sketch_oct31b_tds tdsDev;
// Esp8266 WiFi -------------------------------------------------------------------------------
void initWifiManager();
void configModeCallback(WiFiManager* myWiFiManager);
void saveConfigCallback();
void doWiFiTick();
// Aliyun Mqtt -------------------------------------------------------------------------------
// for 阿里云SDK的头文件
#include <AliyunIoTSDK.h>
// for 发布/订阅功能的头文件
#include <PubSubClient.h>
// #include "src/Sketch_oct31b_aliyun.h"
// Sketch_oct31b_aliyun aliyun;
#define PRODUCT_KEY "a1yJwf7XIoU"
#define DEVICE_NAME "ESP8266-tmp-01"
#define DEVICE_SECRET "0b4631600f778a6a6b3936e5d1763b8b"
#define REGION_ID "cn-shanghai"
String TOPIC_SET_PROPERTY = "/sys/" + String(PRODUCT_KEY) + "/{dName}/thing/service/property/set";
String MQTT_SERVER = String(PRODUCT_KEY) + ".iot-as-mqtt." + String(REGION_ID) + ".aliyuncs.com";  //阿里云MQTT服务地址
void initAliyunManager();
void upload2Aliyun();
// OTHER -------------------------------------------------------------------------------------
void tick();
void onButtonClick();
void doTdsTick();
void doDS18B20Tick();
void doAliyun();
// -------------------------------------------------------------------------------------------
static WiFiClient espClient;
//flag for saving data
bool shouldSaveConfig = false;
// 结构体 存储到eeprom
struct device_config {
  char ssid[20];
  char psw[20];
  uint8_t magic;
} config;
struct Aliyun_data {
  double currentInSideTemperatureA = -1;  // 当前温度
  double currentOutSideTemperatureB = -1;
  double currentHumidity = 0;  // 当前湿度
  double ch2o = 0;             // 二氧化碳浓度
  double tds = 0;              // 当前液体浑浊度
} aliyunData;
// -------------------------------------------------------------------------------------------

/****************************************set up and loop part*********************************/
static long interval_sends = 3;
static long _interval = interval_sends * 1000;
unsigned long l_delayStart = 0;
bool delayRunning = false;  // true if still waiting for delay to finish
float _temperature = -1;
float _tds = -1;
double _ch2o = 500;
double _humidity = 55;

void setup() {
  delay(2000);
  l_delayStart = millis();
  delayRunning = true;  // not finished yet
  // 绑定初始化配置参数按钮行为
  button.attachDoubleClick(onButtonClick);
  // 初始化系统配置
  initSystem();
  // 初始化无线网络配置
  initWifiManager();
  // 停止定时器
  ticker.detach();
  // 提示灯常亮,表示网络已经正常链接
  digitalWrite(BUILTIN_LED, LOW);
  ds18b20 = Sketch_oct31b_ds18b20();
  // 初始化DS18B20设备
  ds18b20.initDS18b60DevManager();
  tdsDev = Sketch_oct31b_tds();
  // 初始化TDS检测设备
  tdsDev.initSEN0244DevManager();
  // aliyun = Sketch_oct31b_aliyun();
  // 初始化阿里云配置
  // aliyun.initAliyunManager(espClient);
  initAliyunManager();
}

void loop() {
  // ds18b20.initDev();
  ESP.wdtFeed();
  button.tick();
  doWiFiTick();
  doDS18B20Tick();
  doTdsTick();
  //  ----------------------------------------------- A
  // 阿里云iOT云数据推送
  if (WiFi.status() == WL_CONNECTED) {
    ticker.attach(0.5, tick);
    // digitalWrite(BUILTIN_LED, HIGH);  // turn led off
    if (delayRunning && (millis() - l_delayStart) >= _interval) {
      delayRunning = false;  // prevent this code being run more then once
      doAliyun();
      delayRunning = true;
      l_delayStart = millis();
    }
  }
  //  ----------------------------------------------- U
}

// -------------------------------------------------------------------------------------------
/**
 * 功能描述：初始化esp8266
 */
void initSystem() {
  Serial.begin(115200);
  Serial.println("Init System");
  for (int i = 0; i < 5; i++) {
    Serial.print(".");
  }
  Serial.println("");
  // 设定LED PIN角为输出角
  pinMode(BUILTIN_LED, OUTPUT);
  // 起始配置WiFi线路AP模式下配置网络。设定LED灯快速闪烁
  ticker.attach(0.6, tick);
}
// -------------------------------------------------------------------------------------------
/**
 * 功能描述：初始化wifimanager
 */
void initWifiManager() {
  /***  步骤一：创建 wifimanager对象 **/
  WiFiManager wifiManager;
  /*************************************/
  /*** 步骤二：进行一系列配置,参考配置类方法 **/
  // 重置保存的修改 目标是为了每次进来都是去掉配置页面
  // wifiManager.resetSettings();
  // 配置连接超时
  wifiManager.setConnectTimeout(60);
  // 打印调试内容
  wifiManager.setDebugOutput(true);
  // 设置最小信号强度
  wifiManager.setMinimumSignalQuality(30);
  // 设置固定AP信息
  IPAddress _ip = IPAddress(192, 168, 4, 25);
  IPAddress _gw = IPAddress(192, 168, 4, 1);
  IPAddress _sn = IPAddress(255, 255, 255, 0);
  wifiManager.setAPStaticIPConfig(_ip, _gw, _sn);
  // 设置进入AP模式的回调
  wifiManager.setAPCallback(configModeCallback);
  // 设置点击保存的回调
  wifiManager.setSaveConfigCallback(saveConfigCallback);
  // 设置 如果配置错误的ssid或者密码 退出配置模式
  wifiManager.setBreakAfterConfig(true);
  // 设置过滤重复的AP 默认可以不用调用 这里只是示范
  wifiManager.setRemoveDuplicateAPs(true);
  /*************************************/
  /*** 步骤三：尝试连接网络,失败去到配置页面 **/
  String mac = WiFi.macAddress();
  mac.replace(":", "");
  String ssid = String("ESP8266-") + "-" + mac;
  if (!wifiManager.autoConnect(ssid.c_str(), "esp8266-tuut")) {
    Serial.println("failed to connect and hit timeout");
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(1000);
  }
  /*************************************/
  // 读取配置页面配置好的信息
  strcpy(config.ssid, wifiManager.getWiFiSSID().c_str());
  strcpy(config.psw, wifiManager.getWiFiPass().c_str());
  // 保存自定义信息
  if (shouldSaveConfig) {
    Serial.println("saving config");
    const size_t capacity = JSON_ARRAY_SIZE(1) + 2 * JSON_OBJECT_SIZE(4) + 60;
    DynamicJsonDocument doc(capacity);
    JsonObject root = doc.to<JsonObject>();
    root["ssid"] = config.ssid;
    root["psw"] = config.psw;
    serializeJson(root, Serial);
    //end save
  }
  Serial.println("");
  Serial.println("load Local IP Address:");
  Serial.print(WiFi.localIP());
  Serial.println("Connected...So Easy :)");
}

/**
 * 功能描述：配置进入AP模式通知回调
 */
void configModeCallback(WiFiManager* myWiFiManager) {
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  //if you used auto generated SSID, print it
  Serial.println(myWiFiManager->getConfigPortalSSID());
  //entered config mode, make led toggle faster
  ticker.attach(0.2, tick);
}

/**
 * 功能描述：设置点击保存的回调
 */
void saveConfigCallback() {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}
// -------------------------------------------------------------------------------------------
void initAliyunManager() {
  String topicSetProperty = TOPIC_SET_PROPERTY;
  // deviceName placeholder替换
  topicSetProperty.replace("{dName}", DEVICE_NAME);
  Serial.println("初始化 iot,需传入 wifi 的 client,和设备产品信息...");
  Serial.println(topicSetProperty);
  const size_t capacity = JSON_ARRAY_SIZE(1) + 2 * JSON_OBJECT_SIZE(4) + 60;
  DynamicJsonDocument doc(capacity);
  JsonObject root = doc.to<JsonObject>();
  root["mqtt_server"] = MQTT_SERVER;
  root["product_key"] = PRODUCT_KEY;
  root["device_name"] = DEVICE_NAME;
  root["device_secret"] = DEVICE_SECRET;
  root["region_id"] = REGION_ID;
  serializeJson(root, Serial);
  Serial.println("初始化阿里云iot");
  // 初始化 iot,需传入 wifi 的 client,和设备产品信息
  AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID);
  // 绑定一个设备属性回调,当远程修改此属性,会触发 powerCallback
  // PowerSwitch 是在设备产品中定义的物联网模型的 id
  Serial.println("绑定阿里云互调事件");
  char* p = (char*)"PowerSwitch";
  AliyunIoTSDK::bindData("PowerSwitch", powerCallback);
  // 发送一个数据到云平台,LightLuminance 是在设备产品中定义的物联网模型的 id
  // AliyunIoTSDK::send("LightLuminance", 100);
}

void upload2Aliyun() {
  Serial.print("向阿里云推送监测数据: ");
  AliyunIoTSDK::loop();
  AliyunIoTSDK::send("insideTemperature", aliyunData.currentInSideTemperatureA);
  AliyunIoTSDK::send("outsideTemperature", aliyunData.currentOutSideTemperatureB);
  AliyunIoTSDK::send("tds", aliyunData.tds);
  AliyunIoTSDK::send("ch2o", aliyunData.ch2o);
  AliyunIoTSDK::send("CurrentTemperature", aliyunData.currentInSideTemperatureA);
  AliyunIoTSDK::send("CurrentHumidity", aliyunData.currentHumidity);
  delay(3000);
}

// 电源属性修改的回调函数
void powerCallback(JsonVariant p) {
  int PowerSwitch = p["PowerSwitch"];
  if (PowerSwitch == 1) {
    // 启动设备
    Serial.print("启动设备");
  } else {
    Serial.print("关闭设备");
  }
}
// -------------------------------------------------------------------------------------------
/**
 * 功能：连接路由心跳函数
 */
void doWiFiTick() {
  static bool taskStarted = false;
  static bool startSTAFlag = false;
  static uint32_t lastWiFiCheckTick = 0;
  if (!startSTAFlag) {
    startSTAFlag = true;
    Serial.print("connect to ap:");
    Serial.println(config.ssid);
    WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(config.ssid, config.psw);
  }
  //未连接1s重连
  if (WiFi.status() != WL_CONNECTED) {
    if (millis() - lastWiFiCheckTick > 1000) {
      lastWiFiCheckTick = millis();
      Serial.print(".");
    }
  }
  //连接成功建立
  else {
    if (taskStarted == false) {
      taskStarted = true;
      Serial.print("\r\nGet IP Address: ");
      Serial.println(WiFi.localIP());
    }
  }
  ESP.wdtFeed();
}

void doDS18B20Tick() {
  ds18b20.loadDS18B20API(_interval);
}

void doTdsTick() {
  _temperature = ds18b20.getDs18b20Temp();
  tdsDev.setDs18b20Temperature(_temperature);
  tdsDev.loadTDSAPI(_interval);
  _tds = tdsDev.getTds();
}

void doAliyun() {
  // aliyun.aliyunData.currentInSideTemperatureA = static_cast<double>(_temperature);
  // aliyun.aliyunData.currentOutSideTemperatureB = static_cast<double>(_temperature);
  // aliyun.aliyunData.currentHumidity = _humidity;
  // aliyun.aliyunData.ch2o = _ch2o;
  // aliyun.aliyunData.tds = static_cast<double>(_tds);
  // aliyun.upload2Aliyun(_interval);
  upload2Aliyun();
}
// -------------------------------------------------------------------------------------------
/**
 * 功能描述：设置LED灯闪烁,提示用户进入配置模式
 */
void tick() {
  //toggle state
  int state = digitalRead(BUILTIN_LED);  // get the current state of GPIO1 pin
  digitalWrite(BUILTIN_LED, !state);     // set pin to the opposite state
}
// -------------------------------------------------------------------------------------------
// 重置
void onButtonClick() {
  Serial.println(F("Button  click."));
  delay(1000);
}
